import java.awt.Color;
import java.awt.Paint;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Stack;

import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import org.apache.commons.collections15.Transformer;

import edu.uci.ics.jung.visualization.VisualizationViewer;


public class Astar {
	ArrayList<Path> paths;
	private Iterator<Node> iter;
	ObjectOutputStream oos = null;
	ObjectInputStream ois = null;
	private Path npath;
	private Transformer<Node, Paint> vertexColor;
	VisualizationViewer<Node, Edge> Viserver;
	private Object lock1;
	JTextArea texpane;
	Node target;

	Astar(Object lock1, JTextArea texpane){
		paths = new ArrayList<Path>();
		this.lock1=lock1;
		this.texpane=texpane;
	}
	private double calculateDistance(Node i, Node j, int Heuristic){
		double d = 0;
		if (Heuristic==0){
			double ydif=Math.pow(j.getPos().getY()-i.getPos().getY(), 2);
			double xdif=Math.pow(j.getPos().getX()-i.getPos().getX(), 2);
			d = Math.sqrt(ydif+xdif);
		}
		else if (Heuristic==1){
			if(j.getID().equals(target)){
				d=0;
			}
			else{
				d=1.0;
			}
			
		}
		return d;
		
	}
	

	// This is the method used to calculate
	public Path calculatePath(final Node source, final Node target,VisualizationViewer<Node, Edge> Viserver,int mode, int H) throws Exception{
		Node cur;
		cur=source;
		this.target=target;
		Path smallest = null;
		npath  = new Path();
		npath.getStack().push(source);
		this.Viserver=Viserver;

		while(cur != target){// Stay in the loop until target is found
			//Create copies of paths
			if(!cur.isVisited()){// If current node has been visited, ignore it.
				cur.setVisited(true);// set current node as visited 
				if (cur.getNeighCount()==0){ // if the neighbors of the current node don't have neighbors do not create a path to that node.
					break;
				}
				else if(cur.getNeighCount()==1){ // if current node has a neighbor just add the neighbor to the current path 
					Node neighbor = cur.getNeighbors().iterator().next();//get current node neighbor
					if (!neighbor.isVisited()){
						if (neighbor.getNeighCount()>0 || neighbor==target){
							Stack<Node> temp=npath.getStack();

							//Straight Line distance to target
							double sld = calculateDistance(neighbor, target,H);

							//Distance to source.
							double nd = calculateDistance(cur, neighbor,H);

							//update cost function
							npath.setHeuristicDistance(sld);
							npath.setSourceDistance(nd);
							npath.updateCostFucnction();

							temp.push(neighbor);
							if (neighbor==target){
								return npath;
							}
						}

					}
				}
				else{// if current node has more than 1 neighbor create as many paths copies as there are neighbors 			
					iter = cur.getNeighbors().iterator();//get current node neighbors
					while (iter.hasNext()){//iterate over the neighbors list
						Node neighbor = iter.next();// get a neighbor
						if (!neighbor.isVisited()){// if neighbor has been visited, ignore it.
							if (neighbor.getNeighCount()>0 || neighbor==target){ //if neighbor has neighbors or neighbor is the target
								Path clone = new Path(npath);// create deep copy of the path 
								Stack<Node> temp=clone.getStack();
								temp.push(neighbor);// add neighbor to the path

								//Straight Line distance to target
								double sld = calculateDistance(neighbor, target,H);

								//Distance to source.
								double nd = calculateDistance(cur, neighbor,H);

								//update cost function
								clone.setHeuristicDistance(sld);
								clone.setSourceDistance(nd);
								clone.updateCostFucnction();

								if (neighbor==target){
									return clone;
								}
								paths.add(clone);
							}

						}


					}
				}
			}

			//Eliminate redundant path
			Iterator<Path> iter_1 = paths.iterator();
			while (iter_1.hasNext()){
				Path p = iter_1.next();
				if (p==npath){
					paths.remove(p);
					if (paths.isEmpty()){
						JOptionPane.showMessageDialog(null, "There's no path to the target");
						return new Path();
					}
					break;
				}
			}				

			//Calculate next node
			iter_1 = paths.iterator();
			if (iter_1.hasNext()){
				smallest=iter_1.next();
				while (iter_1.hasNext()){
					Path p = iter_1.next();
					if (p.getCostFunction()< smallest.getCostFunction()){
						smallest = p;

					}
				}
			}
			else{
				JOptionPane.showMessageDialog(null, "There's no path to the target");
				return new Path();
			}


			npath=smallest;
			
			

			vertexColor = new Transformer<Node, Paint>() {
				@Override
				public Paint transform(Node arg0) {

					if (arg0 == source) {
						return Color.YELLOW;
					}
					else if (arg0 == target) {
						return Color.GREEN;
					}									
					else{										
						for (int i=0;i<npath.getStack().size();i++){
							Node node =npath.getStack().get(i);
							if (node.getID().equals(arg0.getID())){
								return Color.BLUE;
							}
						}
						return Color.WHITE;
					}

				}
			};
			this.Viserver.getRenderContext().setVertexFillPaintTransformer(vertexColor);
			this.Viserver.getParent().repaint();
			
			
			
			cur=smallest.getStack().peek();
			texpaneupdate(npath,H);
			if (mode==0){
				synchronized (lock1) {
					lock1.wait();
				}
			}
			else if (mode==1){
				Thread.sleep(1000);
			}
			
			
		}
		
		return npath;

	}

	void texpaneupdate(Path npPath, int mode){
		String temp = texpane.getText();
		texpane.setText("");
		texpane.append("Calculating:\n");
		if (mode==0){
			texpane.append("Cost Function: "+npath.getCostFunction()+"\n");
			texpane.append("Straight Line Distance: "+npath.getHeuristicDistance()+"\n");
			texpane.append("Distance To Source: "+npath.getSourceDistance()+"\n");
		}
		else if (mode==1){
			texpane.append("Cost Function: "+npath.getCostFunction()+"\n");
			texpane.append("Heuristic: "+npath.getHeuristicDistance()+"\n");
			texpane.append("Links To Source: "+npath.getSourceDistance()+"\n");
		}
		
		for (int i=0;i<npath.getStack().size();i++){					
			texpane.append(npath.getStack().get(i).getID()+"\n");					
		}
		texpane.append("***********************\n");
		texpane.append(temp+"\n");
	}




}
